<!DOCTYPE html>
<html>
<head><meta name="generator" content="Hexo 3.9.0">
  <meta charset="utf-8">
  
  <title>Azure WebApp Logs自动备份到Azure Storage | AI搬砖工</title>
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
  <meta name="description" content="Azure WebApp比自己租用IIS部署ASP.NET或者ASP.NET CORE程序有很大的优势，包括但不限于自动缩放（当然是要给钱的）、自动缩放无需配置负载均衡、Visual Studio集成好、部署上传速度快、不需要担心IIS环境部署容易出错等问题。 但有一些用户会觉得不太适应，因为Azure WebApp并非一台虚拟机，可以理解为一个类似于Docker容器，主要是给你程序运行资源，而不">
<meta name="keywords" content="ASP.NET Core,.NET Core,Azure">
<meta property="og:type" content="article">
<meta property="og:title" content="Azure WebApp Logs自动备份到Azure Storage">
<meta property="og:url" content="https://keithlibrary.io/2018/10/25/20181005 Azure WebApp Logs自动备份到Azure Storage/index.html">
<meta property="og:site_name" content="AI搬砖工">
<meta property="og:description" content="Azure WebApp比自己租用IIS部署ASP.NET或者ASP.NET CORE程序有很大的优势，包括但不限于自动缩放（当然是要给钱的）、自动缩放无需配置负载均衡、Visual Studio集成好、部署上传速度快、不需要担心IIS环境部署容易出错等问题。 但有一些用户会觉得不太适应，因为Azure WebApp并非一台虚拟机，可以理解为一个类似于Docker容器，主要是给你程序运行资源，而不">
<meta property="og:locale" content="zh-CN">
<meta property="og:image" content="https://image.yilingting.com/publics/blogs/20181005/%E5%8A%A0%E5%85%A52.x%E7%89%88%E6%9C%ACWebJobs_Extensions.png">
<meta property="og:image" content="https://image.yilingting.com/publics/blogs/20181005/%E6%B7%BB%E5%8A%A0Microsoft.Azure.Storage.DataMovement%EF%BC%8C%E5%BF%AB%E9%80%9F%E4%BC%A0%E8%BE%93%E6%96%87%E4%BB%B6.png">
<meta property="og:image" content="https://image.yilingting.com/publics/blogs/20181005/%E8%AE%B0%E5%BE%97UseTimers.png">
<meta property="og:image" content="https://image.yilingting.com/publics/blogs/20181005/%E4%BD%BF%E7%94%A8Azure%20WebApp%E7%9B%AE%E5%BD%95%EF%BC%8C%E9%9C%80%E8%A6%81%E4%BD%BF%E7%94%A8%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F%E8%8E%B7%E5%8F%96WebRoot%E8%B7%AF%E5%BE%84.png">
<meta property="og:image" content="https://image.yilingting.com/publics/blogs/20181005/wwwroot%E6%98%AFWebApp%E7%9A%84%E8%B7%AF%E5%BE%84.png">
<meta property="og:image" content="https://image.yilingting.com/publics/blogs/20181005/%E6%9C%80%E5%A5%BD%E8%AE%BE%E7%BD%AE%E8%A6%86%E7%9B%96%E6%97%A5%E5%BF%97%EF%BC%8C%E5%90%A6%E5%88%99%E9%81%87%E5%88%B0%E5%90%8C%E5%90%8DCloudBlob%E4%BC%9A%E7%88%86%E5%BC%82%E5%B8%B8.png">
<meta property="og:image" content="https://image.yilingting.com/publics/blogs/20181005/%E9%9C%80%E8%A6%81%E5%8D%87%E7%BA%A7Azure%20WebApp%E5%88%B0B1%E7%BA%A7%E5%88%AB.png">
<meta property="og:image" content="https://image.yilingting.com/publics/blogs/20181005/B1%E7%BA%A7%E5%88%AB%E8%B4%B9%E7%94%A8%E6%98%8E%E6%98%BE%E4%B8%8A%E5%8E%BB%E4%BA%86.png">
<meta property="og:updated_time" content="2019-09-06T07:04:15.819Z">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="Azure WebApp Logs自动备份到Azure Storage">
<meta name="twitter:description" content="Azure WebApp比自己租用IIS部署ASP.NET或者ASP.NET CORE程序有很大的优势，包括但不限于自动缩放（当然是要给钱的）、自动缩放无需配置负载均衡、Visual Studio集成好、部署上传速度快、不需要担心IIS环境部署容易出错等问题。 但有一些用户会觉得不太适应，因为Azure WebApp并非一台虚拟机，可以理解为一个类似于Docker容器，主要是给你程序运行资源，而不">
<meta name="twitter:image" content="https://image.yilingting.com/publics/blogs/20181005/%E5%8A%A0%E5%85%A52.x%E7%89%88%E6%9C%ACWebJobs_Extensions.png">
  
    <link rel="alternate" href="/atom.xml" title="AI搬砖工" type="application/atom+xml">
  
  
    <link rel="icon" href="https://image.yilingting.com/publics/LOGOs/bzgsoft/favicon.ico">
  
  
    
  
  <link rel="stylesheet" href="/css/style.css">
  

</head>
</html>
<body>
  <div id="container">
    <div id="wrap">
      <header id="header">
  <div id="banner"></div>
  <div id="header-outer" class="outer">
    
    <div id="header-inner" class="inner">
      <nav id="sub-nav">
        
          <a id="nav-rss-link" class="nav-icon" href="/atom.xml" title="RSS Feed"></a>
        
        <a id="nav-search-btn" class="nav-icon" title="搜索"></a>
      </nav>
      <div id="search-form-wrap">
        <form action="//google.com/search" method="get" accept-charset="UTF-8" class="search-form"><input type="search" name="q" class="search-form-input" placeholder="Search"><button type="submit" class="search-form-submit">&#xF002;</button><input type="hidden" name="sitesearch" value="https://keithlibrary.io"></form>
      </div>
      <nav id="main-nav">
        <a id="main-nav-toggle" class="nav-icon"></a>
        
          <a class="main-nav-link" href="/">首页</a>
        
          <a class="main-nav-link" href="/archives">归档</a>
        
          <a class="main-nav-link" href="/about">关于</a>
        
      </nav>
      
    </div>
    <div id="header-title" class="inner">
      <h1 id="logo-wrap">
        <a href="/" id="logo">AI搬砖工</a>
      </h1>
      
        <h2 id="subtitle-wrap">
          <a href="/" id="subtitle">以足球和NBA球迷的角度讲互联网技术和反心灵鸡汤，顺便聊聊怎么优雅地怼上司。</a>
        </h2>
      
    </div>
  </div>
</header>
      <div class="outer">
        <section id="main"><article id="post-20181005 Azure WebApp Logs自动备份到Azure Storage" class="article article-type-post" itemscope itemprop="blogPost">
  <div class="article-meta">
    <a href="/2018/10/25/20181005 Azure WebApp Logs自动备份到Azure Storage/" class="article-date">
  <time datetime="2018-10-25T03:22:33.000Z" itemprop="datePublished">2018-10-25</time>
</a>
    
  </div>
  <div class="article-inner">
    
    
      <header class="article-header">
        
  
    <h1 class="article-title" itemprop="name">
      Azure WebApp Logs自动备份到Azure Storage
    </h1>
  

      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <!-- Table of Contents -->
        
        <p>Azure WebApp比自己租用IIS部署ASP.NET或者ASP.NET CORE程序有很大的优势，包括但不限于自动缩放（当然是要给钱的）、自动缩放无需配置负载均衡、Visual Studio集成好、部署上传速度快、不需要担心IIS环境部署容易出错等问题。</p>
<p>但有一些用户会觉得不太适应，因为Azure WebApp并非一台虚拟机，可以理解为一个类似于Docker容器，主要是给你程序运行资源，而不是给你用于存储的。Azure WebApp连Web访问日志和系统日志都是需要配置到一个Azure Storage Blob存储里面的，所以它的理念是让存储文件更加“分布式”——就是多个WebApp应该都把日志存放在一个集中的Storage里面。</p>
<p>但是这个给传统程序带来一些不方便，使用log4net、nlog等日志写入组件会需要写入磁盘，而Azure Storage不方便挂载到WebApp里面。如果日志写多了，很可能把Azure WebApp写爆。</p>
<p>那么有两种方法，一种是自定义一个log4net、nlog等的日志Appender，让日志写入到Azure Storage Blob存储或者Table存储里面都可以，这部分代码在GitHub上有一些。配置Appender到log4net.config或者nlog.config就可以使用。</p>
<p>但是笔者在自己试过写一个写入Azure Storage Table的日志记录程序之后，发现大部分情况下Azure Storage Table方案显得有点不方便，有一种更简单的适合更小型程序的方式，就是把log4net或者nlog生成的日志文件归档到Azure Storage Blob里面。</p>
<p>以下就是这个自动备份的程序代码：</p>
<p>Gitee代码Repository：<a href="https://gitee.com/gzkeith/webapp-logs-backup" target="_blank" rel="noopener">https://gitee.com/gzkeith/webapp-logs-backup</a></p>
<p>首先需要建一个WebJob项目，直接在Visual Studio里面建就可以了，但我们如果要定时，则需要加入Microsoft.Azure.WebJobs.Extensions，使用Nuget添加：</p>
<p><img src="https://image.yilingting.com/publics/blogs/20181005/%E5%8A%A0%E5%85%A52.x%E7%89%88%E6%9C%ACWebJobs_Extensions.png" alt></p>
<p>注意我们使用的是2.x版本，需要保证Azure.WebJobs Nuget包和Extensions Nuget包同版本。现在已经有3.0版本刚刚出来了，写法完全不一样，截止到今天网上找不到多少参考，所以千万别直接升级到3.0或以后。</p>
<p>而目前Visual Studio也没有.net core的WebJob项目模板，暂时还是用.NET Framework 4.6来做吧。</p>
<p>WebJobs项目应该自带WindowsAzure.Storage，这个是使用Storage Blob存储必须的。然后笔者是强迫症升级了最新版本了，再之后，加入一个方便处理的包：Microsoft.Azure.Storage.DataMovement</p>
<p><img src="https://image.yilingting.com/publics/blogs/20181005/%E6%B7%BB%E5%8A%A0Microsoft.Azure.Storage.DataMovement%EF%BC%8C%E5%BF%AB%E9%80%9F%E4%BC%A0%E8%BE%93%E6%96%87%E4%BB%B6.png" alt></p>
<p>这个包是微软自己写的，用来高效读写Storage的。用了它，减少处理二进制文件的复杂度。使用里面的TransportManager，直接把文件路径或者Stream传进去，设置一个缓存，UploadAsync就会自动分块多线程上传，不用自己做byte[]缓存了，能够在想要简单解决问题的时候满足我们的需要。</p>
<p>加好了Nuget包，记得在启动前设置UseTimers，不然cron表达式会不起作用：</p>
<p><img src="https://image.yilingting.com/publics/blogs/20181005/%E8%AE%B0%E5%BE%97UseTimers.png" alt></p>
<p>然后我建立一个定时的WebJobs函数，因为是Debug需要，就设置为2分钟一次，实际上要按照日志文件的分割逻辑、写入空间量设定一个合理的备份时间避免写爆磁盘空间，又不要经常做无必要的轮询：</p>
<p><img src="https://image.yilingting.com/publics/blogs/20181005/%E4%BD%BF%E7%94%A8Azure%20WebApp%E7%9B%AE%E5%BD%95%EF%BC%8C%E9%9C%80%E8%A6%81%E4%BD%BF%E7%94%A8%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F%E8%8E%B7%E5%8F%96WebRoot%E8%B7%AF%E5%BE%84.png" alt></p>
<p>上面这里用了一个环境变量路径：WEBROOT_PATH</p>
<p>这是Azure WebApp自带的，能够直接进到WebApp的运行路径。如果通过GetCurrentDirectory获取执行位置，只会获取到一个临时文件位置，因为WebApp运行会先Copy到一个临时文件位置避免执行时锁死文件路径。</p>
<p>笔者这边使用nlog，直接把文件写到logs目录底下了：</p>
<p><img src="https://image.yilingting.com/publics/blogs/20181005/wwwroot%E6%98%AFWebApp%E7%9A%84%E8%B7%AF%E5%BE%84.png" alt></p>
<p>所以直接组合了DirectoryInfo。</p>
<p>后面就是迭代里面的文件了。笔者直接使用nlog的.net core日志模式，文件名是nlog-all-yyyy-MM-dd.log和nlog-own-yyyy-MM-dd.log这样的格式。于是就扫描里面所有的nlog-*.log，除掉当前（必须使用UTC时间，WebApp上面运行是使用UTC时间的）的日志，其他的日志都归档。</p>
<p>这部分请直接参见代码，就不贴图了。</p>
<p>有一个位置需要注意的，使用DataMovement包的TransferManager.UploadAsync最好配置一下如果遇到文件重名就overwrite，否则会报错：</p>
<p><img src="https://image.yilingting.com/publics/blogs/20181005/%E6%9C%80%E5%A5%BD%E8%AE%BE%E7%BD%AE%E8%A6%86%E7%9B%96%E6%97%A5%E5%BF%97%EF%BC%8C%E5%90%A6%E5%88%99%E9%81%87%E5%88%B0%E5%90%8C%E5%90%8DCloudBlob%E4%BC%9A%E7%88%86%E5%BC%82%E5%B8%B8.png" alt></p>
<p>接着就是运行问题了。要保证WebJob按时触发不延迟，是需要把WebApp的“始终可用”打开的：</p>
<p><img src="https://image.yilingting.com/publics/blogs/20181005/%E9%9C%80%E8%A6%81%E5%8D%87%E7%BA%A7Azure%20WebApp%E5%88%B0B1%E7%BA%A7%E5%88%AB.png" alt></p>
<p>但是打开这个，显然收费的级别就最少要到B1，费用降不下去了：</p>
<p><img src="https://image.yilingting.com/publics/blogs/20181005/B1%E7%BA%A7%E5%88%AB%E8%B4%B9%E7%94%A8%E6%98%8E%E6%98%BE%E4%B8%8A%E5%8E%BB%E4%BA%86.png" alt></p>
<p>WebApp的App Service Plan上去之后，有10GB存储就没那么容易写爆了，好像意义没那么大了。</p>
<p>其实也是可以在ASP.net程序里面做一个Web API接口，按照存储需要手动触发日志备份，这样就可以经济一点，但就需要做一个自己轮询自己的功能，隔一分钟调用自己一次进行扫描，扫描结束之后再延时调用自己下一次轮询。</p>

      
    </div>
    <footer class="article-footer">
      <a data-url="https://keithlibrary.io/2018/10/25/20181005 Azure WebApp Logs自动备份到Azure Storage/" data-id="ck07sonf70000ugkg1xv6xs0g" class="article-share-link">分享</a>
      
      
      
  <ul class="article-tag-list"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/NET-Core/">.NET Core</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/ASP-NET-Core/">ASP.NET Core</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/Azure/">Azure</a></li></ul>

    </footer>
  </div>
  
    
 <script src="/jquery/jquery.min.js"></script>
  <div id="random_posts">
    <h2>推荐文章</h2>
    <div class="random_posts_ul">
      <script>
          var random_count =4
          var site = {BASE_URI:'/'};
          function load_random_posts(obj) {
              var arr=site.posts;
              if (!obj) return;
              // var count = $(obj).attr('data-count') || 6;
              for (var i, tmp, n = arr.length; n; i = Math.floor(Math.random() * n), tmp = arr[--n], arr[n] = arr[i], arr[i] = tmp);
              arr = arr.slice(0, random_count);
              var html = '<ul>';
            
              for(var j=0;j<arr.length;j++){
                var item=arr[j];
                html += '<li><strong>' + 
                item.date + ':&nbsp;&nbsp;<a href="' + (site.BASE_URI+item.uri) + '">' + 
                (item.title || item.uri) + '</a></strong>';
                if(item.excerpt){
                  html +='<div class="post-excerpt">'+item.excerpt+'</div>';
                }
                html +='</li>';
                
              }
              $(obj).html(html + '</ul>');
          }
          $('.random_posts_ul').each(function () {
              var c = this;
              if (!site.posts || !site.posts.length){
                  $.getJSON(site.BASE_URI + 'js/posts.js',function(json){site.posts = json;load_random_posts(c)});
              } 
               else{
                load_random_posts(c);
              }
          });
      </script>
    </div>
  </div>

    
<nav id="article-nav">
  
  
    <a href="/2018/08/25/20180825 ASP.NET Core解决IIS部署2008R2失败与反向代理的问题/" id="article-nav-older" class="article-nav-link-wrap">
      <strong class="article-nav-caption">下一篇</strong>
      <div class="article-nav-title">ASP.NET Core + IIS 502.5错误码0x8007010b的解决方法</div>
    </a>
  
</nav>

  
</article>
 
     
  <div class="comments" id="comments">
    
     
       
      <div id="cloud-tie-wrapper" class="cloud-tie-wrapper"></div>
    
       
      
      
  </div>
 
  

</section>
           
    <aside id="sidebar">
  
    

  
    
  
    

  
    
  
    
  <div class="widget-wrap">
    <h3 class="widget-title recent-posts">最新文章</h3>
    <div class="widget">
      <ul>
        
          <li>
            <a href="/2018/10/25/20181005 Azure WebApp Logs自动备份到Azure Storage/">Azure WebApp Logs自动备份到Azure Storage</a>
          </li>
        
          <li>
            <a href="/2018/08/25/20180825 ASP.NET Core解决IIS部署2008R2失败与反向代理的问题/">ASP.NET Core + IIS 502.5错误码0x8007010b的解决方法</a>
          </li>
        
      </ul>
    </div>
  </div>

  
    

  
    
  
    <!--微信公众号二维码-->


  
</aside>

      </div>
      <footer id="footer">
  
  <div class="outer">
    <div id="footer-left">
      粤ICP备16108200&nbsp;|&nbsp;
      &copy; 2019 AI搬砖工&nbsp;|&nbsp;
      主题 <a href="https://github.com/giscafer/hexo-theme-cafe/" target="_blank">Cafe</a>
    </div>
    <div id="footer-right">
      联系方式&nbsp;|&nbsp;keithlibrary@outlook.com
    </div>
  </div>
</footer>
 <script src="/jquery/jquery.min.js"></script>
    </div>
    <nav id="mobile-nav">
  
    <a href="/" class="mobile-nav-link">首页</a>
  
    <a href="/archives" class="mobile-nav-link">归档</a>
  
    <a href="/about" class="mobile-nav-link">关于</a>
  
</nav>
    <img class="back-to-top-btn" src="/images/fly-to-top.png"/>
<script>
// Elevator script included on the page, already.
window.onload = function() {
  var elevator = new Elevator({
    selector:'.back-to-top-btn',
    element: document.querySelector('.back-to-top-btn'),
    duration: 1000 // milliseconds
  });
}
</script>
      

  
    <script>
      var cloudTieConfig = {
        url: document.location.href, 
        sourceId: "",
        productKey: "e2fb4051c49842688ce669e634bc983f",
        target: "cloud-tie-wrapper"
      };
    </script>
    <script src="https://img1.ws.126.net/f2e/tie/yun/sdk/loader.js"></script>
    

  







<!-- author:forvoid begin -->
<!-- author:forvoid begin -->

<!-- author:forvoid end -->

<!-- author:forvoid end -->


  
    <script type="text/x-mathjax-config">
      MathJax.Hub.Config({
        tex2jax: {
          inlineMath: [ ['$','$'], ["\\(","\\)"]  ],
          processEscapes: true,
          skipTags: ['script', 'noscript', 'style', 'textarea', 'pre', 'code']
        }
      })
    </script>

    <script type="text/x-mathjax-config">
      MathJax.Hub.Queue(function() {
        var all = MathJax.Hub.getAllJax(), i;
        for (i=0; i < all.length; i += 1) {
          all[i].SourceElement().parentNode.className += ' has-jax';
        }
      })
    </script>
    <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
  


 <script src="/js/is.js"></script>


  <link rel="stylesheet" href="/fancybox/jquery.fancybox.css">
  <script src="/fancybox/jquery.fancybox.pack.js"></script>


<script src="/js/script.js"></script>
<script src="/js/elevator.js"></script>
  </div>
</body>
</html>